package com.tang.common.config;

import com.tang.common.filter.*;
import com.tang.common.properties.TaoSystemProperties;
import com.tang.module.system.entity.User;
import com.tang.module.system.service.UserService;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;

/**
 * 权限框架配置
 * @author tang
 * @date 2021/10/24 12:52
 */
@Configuration
@EnableWebSecurity
@EnableConfigurationProperties(TaoSystemProperties.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private UserService userService;

    @Resource
    private AccessDecisionManagerConfig accessDecisionManagerConfig;

    @Resource
    private UrlFilterConfig urlFilterConfig;

    @Resource
    private RestAccessDeniedHandler restAccessDeniedHandler;

    @Resource
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;


    /**
     * 放行资源
     * 不会走过滤器链
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web){
        web.ignoring()
                //放行资源
                .antMatchers("/favicon.ico")
                .antMatchers("/doc.html")
                .antMatchers("/swagger-resources")
                .antMatchers("/webjars/**")
                .antMatchers("/v2/api-docs/**")
                .antMatchers("/captcha")
                .antMatchers("/login")
                .antMatchers("/error")
                .antMatchers("/user/importExcel")
                .antMatchers("/use/**")
                .antMatchers("/test/**");
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭csrf和不使用session
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        //在token过滤器中实现认证
        http.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        //授权
        http.authorizeRequests()
                //请求都需要验证,并且设置动态权限
                .anyRequest().authenticated()
                //动态权限设置
                //FilterSecurityInterceptor 作为 Spring Security Filter Chain 的最后一个 Filter
                // （1）查询出request所需的角色
                // （2）判断用户是否具有该角色从而允许或拒绝
                //查询request所需角色是从FilterInvocationSecurityMetadataSource中获取的，可以重写这个方法从数据库中获取request所需的角色，
                // 但考虑每次请求都访问数据库比较浪费性能，如果url数量不多，可以考虑一次取完。
                //判断用户是否具有该角色从而允许或拒绝是在AccessDecisionManager的decide方法中执行的。
                // 逻辑主要是比对request所需角色和用户已有角色，如果匹配，就允许访问，否则拒绝。如果要实现自己的判断逻辑就要重写decide方法。
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        //可以访问本次请求url的角色集合
                        o.setSecurityMetadataSource(urlFilterConfig);
                        //和上下文中保存的角色进行对比
                        o.setAccessDecisionManager(accessDecisionManagerConfig);
                        return o;
                    }
                })
                .and()
                //不需要缓存
                .headers()
                .cacheControl();

        //不使用缓存
        http.headers().cacheControl();


        //认证和授权中的异常处理类
        http.exceptionHandling()
                //权限不足
                .accessDeniedHandler(restAccessDeniedHandler)
                //未登录或者token过期
                .authenticationEntryPoint(restAuthenticationEntryPoint);
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService(){
        return  username -> {
            User user = userService.userDetails(username);
            if (user == null){
                throw new UsernameNotFoundException("用户名不存在");
            }
            return user;
        };
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }


    @Bean
    public JwtTokenFilter jwtTokenFilter(){
        return new JwtTokenFilter();
    }

}
